package com.tmt.im.service.service;

import com.alibaba.fastjson2.JSONObject;
import com.tmt.im.common.enums.CustomizedMsgCode;
import com.tmt.im.common.enums.IMAuthorityEnum;
import com.tmt.im.common.pojo.ChatSessionMemberPoJo;
import com.tmt.im.common.pojo.ChatSessionPoJo;
import com.tmt.im.common.pojo.IMUserPoJo;
import com.tmt.im.common.pojo.SessionMemberKey;
import com.tmt.im.common.store.StoreSqlHelper;
import com.tmt.im.common.yunxin.IYunXinMsgSender;
import com.tmt.im.service.controller.vo.*;
import com.tmt.im.service.helper.IgniteQueryHelper;
import com.tmt.im.service.helper.SessionNotifyHelper;
import com.tmt.springboot.common.helper.IdSnowFlake;
import com.tmt.springboot.common.restful.ExecuteResult;
import com.tmt.springboot.common.restful.IExecuteResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.ignite.Ignite;
import org.apache.ignite.cache.query.FieldsQueryCursor;
import org.apache.ignite.cache.query.SqlFieldsQuery;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.security.Principal;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description TODO
 * @Author wuyi
 * @Date 2024/8/30 15:15
 * @Version 1.0
 **/
@Slf4j
@Service
public class SessionMemberService extends BaseService {


    private final IYunXinMsgSender iYunXinMsgSender;

    public SessionMemberService(Ignite ignite,
                                IYunXinMsgSender iYunXinMsgSender) {
        super(ignite);
        this.iYunXinMsgSender = iYunXinMsgSender;
    }

    /**
     * 邀请人加入群组
     *
     * @param currentUserId
     * @param request
     * @param principal
     * @return
     */
    public IExecuteResult<JoinSessionResponse> joinSession(Long currentUserId, JoinSessionRequest request,
                                                           Principal principal, List<OperationFailInfo> failInfos) {
        if (failInfos == null) {
            failInfos = new ArrayList<>();
        }

        ChatSessionPoJo pojo = sessionCache.get(request.getTid());

        //是否是群主
        boolean isOwner = isSessionOwner(currentUserId, pojo);
        //是否是管理员
        boolean isManager = isManager(currentUserId, request.getTid());
        //是否有群管理权限
        boolean hasAuthenticated = hasAuthenticated(principal, List.of(IMAuthorityEnum.IM_AUTH_ALL, IMAuthorityEnum.IM_AUTH_SESSION_ALL));
        if (pojo.getInviteMode() == 0 && !(isOwner || isManager || hasAuthenticated)) {
            return ExecuteResult.failure("仅有群主和管理员才能拉人入群", null);
        }

        Map<SessionMemberKey, ChatSessionMemberPoJo> memberPoJos = new HashMap<>();
        //如果入群不需要审核，或者当前用户是群主或管理员，则不需要审核，直接设置审核状态为通过（1）。
        int checkStatus = pojo.getJoinMode() == 0 || isOwner || isManager ? 1 : 0;

        for (Long memberId : request.getMemberIds()) {
            if (failInfos.stream().anyMatch(x -> Objects.equals(x.getMemberId(), memberId))) {
                //如果是加入失败的用户，则跳过继续处理下一个。
                continue;
            }
            if (inSession(memberId, pojo)) {
                //如果memberId已经在群中了，则跳过。
                continue;
            }
            IMUserPoJo userPoJo = userCache.get(memberId);
            Long tmpId = IdSnowFlake.getId();
            Timestamp timestamp = new Timestamp(System.currentTimeMillis());
            ChatSessionMemberPoJo newMemberPoJo = ChatSessionMemberPoJo.builder()
                    .id(tmpId)
                    .mid(tmpId)
                    .memberId(memberId)
                    .memberNick(userPoJo.getNickname())
                    .sessionId(pojo.getId())
                    .inviteMsg(request.getInviteMsg())
                    //禁止抢红包
                    .forbidRedPackage(true)
                    .doNotDisturb(false)
                    .roleInSession(3)
                    .chatBanned(false)
                    .deleted(false)
                    .deleteTime(null)
                    .checkStatus(checkStatus)
                    .checkTime(checkStatus == 1 ? timestamp : null)
                    .checkerId(checkStatus == 1 && (isOwner || isManager) ? currentUserId : null)
                    .agreeStatus(1)
                    .agreeTime(timestamp)
                    .version(0L)
                    .creatorId(currentUserId)
                    .createTime(timestamp)
                    .insert(true)
                    .build();
            memberPoJos.put(newMemberPoJo.getKey(), newMemberPoJo);
        }

        //批量保存新群成员记录。
        sessionMemberCache.putAll(memberPoJos);

        if (checkStatus != 1) {
            //向群主和管理员发送自定义入群待审通知。
            //获取目标群组的群主和管理员。
            String sql = MessageFormat.format("select * from {0} where checkStatus = 1 and roleInSession <=2 and sessionId =?",
                    ChatSessionMemberPoJo.CACHE_NAME);
            List<ChatSessionMemberPoJo> managers = IgniteQueryHelper.findAllBy(sql, new Object[]{request.getTid()},
                    ChatSessionMemberPoJo.class, sessionMemberCache);
            JSONObject json = new JSONObject();
            json.put("type", CustomizedMsgCode.GROUP_NEWS_TYPE.getType());
            json.put("msg", "入群邀请待审通知");
            StringBuilder error = new StringBuilder();
            for (ChatSessionMemberPoJo manager : managers) {
                int total = totalJoinApply(manager.getMemberId());
                json.put("num", total);
                if (!iYunXinMsgSender.sendCustomizedMsg(currentUserId, manager.getMemberId(), json, error)) {
                    log.error("-----发送入群邀请待审通知失败-----" + error);
                }
            }
        }

        //构造返回对象。
        JoinSessionResponse response = new JoinSessionResponse();
        BeanUtils.copyProperties(request, response);
        response.setFailInfos(failInfos);

        return ExecuteResult.success(response);
    }

    /**
     * 群主或管理员审核完毕后处理:
     * 需要更新审核相关字段.如果不需要被邀请人审核,则还需要更新agree相关字段.
     *
     * @param currentUserId
     * @param sessionMemberPoJo
     * @param checkStatus
     * @param agreeMode
     * @return
     */
    public IExecuteResult<ChatSessionMemberPoJo> reviewInvitation(Long currentUserId, ChatSessionMemberPoJo sessionMemberPoJo,
                                                                  int checkStatus, int agreeMode) {
        if (sessionMemberPoJo != null) {
            sessionMemberPoJo.setInsert(false);
            Timestamp timestamp = new Timestamp(System.currentTimeMillis());
            if (!currentUserId.equals(sessionMemberPoJo.getMemberId())) {
                sessionMemberPoJo.setCheckerId(currentUserId);
                sessionMemberPoJo.setCheckStatus(checkStatus);
                sessionMemberPoJo.setCheckTime(timestamp);
            }

            if (currentUserId.equals(sessionMemberPoJo.getMemberId()) || agreeMode == 1) {
                //直接入群.
                sessionMemberPoJo.setAgreeStatus(1);
                sessionMemberPoJo.setAgreeTime(timestamp);
            }
            sessionMemberCache.put(sessionMemberPoJo.getKey(), sessionMemberPoJo);

            if (checkStatus == 1) {
                SessionNotifyHelper.sendChangedNotify(currentUserId, sessionMemberPoJo.getSessionId(),
                        CustomizedMsgCode.JOIN_GROUP, sessionMemberPoJo.getMemberId(), sessionMemberCache, iYunXinMsgSender);
            }

            return ExecuteResult.success(sessionMemberPoJo);
        }
        return ExecuteResult.failure("未知错误", null);
    }

    public IExecuteResult<ChatSessionMemberPoJo> replyInvitation(ChatSessionMemberPoJo sessionMemberPoJo, int agreeStatus) {
        sessionMemberPoJo.setAgreeStatus(agreeStatus);
        sessionMemberPoJo.setAgreeTime(new Timestamp(System.currentTimeMillis()));
        sessionMemberCache.put(sessionMemberPoJo.getKey(), sessionMemberPoJo);
        return ExecuteResult.success(sessionMemberPoJo);
    }

    public IExecuteResult<KickOutSessionResponse> kickOutSession(Long operatorId, KickOutSessionRequest request,
                                                                 Principal principal, List<OperationFailInfo> failInfos) {
        ChatSessionPoJo pojo = sessionCache.get(request.getTid());

        //是否是群主
        boolean isOwner = isSessionOwner(operatorId, pojo);
        //是否是管理员
        boolean isManager = isManager(operatorId, request.getTid());
        //是否有群管理权限
        boolean hasAuthenticated = hasAuthenticated(principal, List.of(IMAuthorityEnum.IM_AUTH_ALL, IMAuthorityEnum.IM_AUTH_SESSION_ALL));
        if (isOwner || isManager || hasAuthenticated) {
            try {
                List<Long> failedIds = failInfos.stream().map(OperationFailInfo::getMemberId).toList();
                List<Long> successList = request.getMemberIds().stream().filter(x -> !failedIds.contains(x)).toList();
                if (!CollectionUtils.isEmpty(successList)) {
                    List<Object> paramObjs = new ArrayList<>();
                    paramObjs.add(request.getTid());

                    StringBuilder params = new StringBuilder();
                    for (int i = 0; i < successList.size(); i++) {
                        paramObjs.add(successList.get(i));
                        params.append(" or memberId = ?");
                    }

                    String sql = MessageFormat.format("select * from {0} where sessionId = ? and ({1})",
                            ChatSessionMemberPoJo.CACHE_NAME, params.substring(4));
                    List<ChatSessionMemberPoJo> sessionMemberPoJos = IgniteQueryHelper.findAllBy(sql, paramObjs.toArray(),
                            ChatSessionMemberPoJo.class, sessionMemberCache);

                    Set<SessionMemberKey> keys = sessionMemberPoJos.stream().map(ChatSessionMemberPoJo::getKey).collect(Collectors.toSet());
                    sessionMemberCache.removeAll(keys);

                    //向群成员发送一条成员被踢出群的自定义通知消息。
                    for (Long memberId : request.getMemberIds()) {
                        SessionNotifyHelper.sendChangedNotify(operatorId, request.getTid(), CustomizedMsgCode.KICK_OUT,
                                memberId, sessionMemberCache, iYunXinMsgSender);
                    }
                }
            } catch (Exception ex) {
                return ExecuteResult.failure(ex.getMessage(), null);
            }
        }

        //构造返回对象。
        KickOutSessionResponse response = new KickOutSessionResponse();
        BeanUtils.copyProperties(request, response);
        response.setFailInfos(failInfos);
        return ExecuteResult.success(response);
    }

    public IExecuteResult<Boolean> leaveSession(Long currentUserId, Long tid, Principal principal) {
        ChatSessionPoJo pojo = sessionCache.get(tid);
        if (pojo == null) {
            return ExecuteResult.failure("目标群组不存在", null);
        }

        String sql = MessageFormat.format("select * from {0} where agreeStatus = ? and sessionId = ? and memberId = ?",
                ChatSessionMemberPoJo.CACHE_NAME);
        List<ChatSessionMemberPoJo> sessionMemberPoJos = IgniteQueryHelper.findAllBy(sql, new Object[]{1, tid, currentUserId},
                ChatSessionMemberPoJo.class, sessionMemberCache);
        for (ChatSessionMemberPoJo memberPoJo : sessionMemberPoJos) {
            //如果存在未领取的红包，该红包超时后会自动退回。
            sessionMemberCache.remove(memberPoJo.getKey());
        }

        //向群成员发送一条成员主动退群的自定义通知消息。
        SessionNotifyHelper.sendChangedNotify(currentUserId, tid, CustomizedMsgCode.QUIT_GROUP,
                currentUserId, sessionMemberCache, iYunXinMsgSender);

        return ExecuteResult.success(true);
    }

    public IExecuteResult<Boolean> updateMemberNick(Long currentUserId, UpdateMemberNickRequest request, Principal principal) {
        String sql = MessageFormat.format("select * from {0} where agreeStatus = ? and sessionId = ? and memberId = ?",
                ChatSessionMemberPoJo.CACHE_NAME);
        ChatSessionMemberPoJo sessionMemberPoJo = IgniteQueryHelper.findOneBy(sql, new Object[]{1, request.getTid(), request.getMemberId()},
                ChatSessionMemberPoJo.class, sessionMemberCache);
        if (sessionMemberPoJo != null) {
            sessionMemberPoJo.setMemberNick(request.getNick());
            sessionMemberCache.put(sessionMemberPoJo.getKey(), sessionMemberPoJo);
            return ExecuteResult.success(true);
        }
        return ExecuteResult.failure("未找到用户在目标群中的记录", false);
    }

    public IExecuteResult<Boolean> updateForbidRedPackage(Long currentUserId, UpdateForbidRedPackageRequest request, Principal principal) {
        String sql = MessageFormat.format("select * from {0} where checkStatus = ? and sessionId = ? and memberId = ?",
                ChatSessionMemberPoJo.CACHE_NAME);
        ChatSessionMemberPoJo sessionMemberPoJo = IgniteQueryHelper.findOneBy(sql, new Object[]{1, request.getTid(), request.getMemberId()},
                ChatSessionMemberPoJo.class, sessionMemberCache);
        if (sessionMemberPoJo != null) {
            sessionMemberPoJo.setForbidRedPackage(request.getAllow());
            sessionMemberCache.put(sessionMemberPoJo.getKey(), sessionMemberPoJo);
            return ExecuteResult.success(true);
        }
        return ExecuteResult.failure("未找到用户在目标群中的记录", false);
    }

    public IExecuteResult<ChatBannedResponse> bannedMembers(Long currentUserId, ChatBannedRequest request,
                                                            Principal principal, List<OperationFailInfo> failInfos) {
        ChatSessionPoJo pojo = sessionCache.get(request.getTid());
        if (pojo == null) {
            return ExecuteResult.failure("目标群组不存在", null);
        }

        //是否是群主
        boolean isOwner = isSessionOwner(currentUserId, pojo);
        //是否是管理员
        boolean isManager = isManager(currentUserId, request.getTid());
        //是否有群管理权限
        boolean hasAuthenticated = hasAuthenticated(principal, List.of(IMAuthorityEnum.IM_AUTH_ALL, IMAuthorityEnum.IM_AUTH_SESSION_ALL));
        if (isOwner || isManager || hasAuthenticated) {
            List<Long> failIds = failInfos.stream().map(OperationFailInfo::getMemberId).toList();
            List<Long> successList = request.getMemberIds().stream().filter(x -> !failIds.contains(x)).toList();

            List<Object> objectParams = new ArrayList<>();
            objectParams.add(1);
            objectParams.add(request.getTid());
            StringBuilder params = new StringBuilder();
            for (int i = 0; i < successList.size(); i++) {
                objectParams.add(successList.get(i));
                params.append(" or memberId = ?");
            }

            String sql = MessageFormat.format("select * from {0} where agreeStatus = ? and sessionId = ? and ({1})",
                    ChatSessionMemberPoJo.CACHE_NAME, params.substring(4));
            List<ChatSessionMemberPoJo> sessionMemberPoJos = IgniteQueryHelper.findAllBy(sql, objectParams.toArray(),
                    ChatSessionMemberPoJo.class, sessionMemberCache);
            if (!CollectionUtils.isEmpty(sessionMemberPoJos)) {
                Map<SessionMemberKey, ChatSessionMemberPoJo> map = new LinkedHashMap<>();
                for (ChatSessionMemberPoJo memberPoJo : sessionMemberPoJos) {
                    memberPoJo.setChatBanned(request.getChatBanned());
                    map.put(memberPoJo.getKey(), memberPoJo);
                }
                sessionMemberCache.putAll(map);
            }

        }

        //构造返回对象。
        ChatBannedResponse response = new ChatBannedResponse();
        BeanUtils.copyProperties(request, response);
        response.setFailInfos(failInfos);

        return ExecuteResult.success(response);
    }

    public IExecuteResult<Boolean> noDisturbingMember(Long currentUserId, NoDisturbingRequest request, Principal principal) {
        String sql = MessageFormat.format("select * from {0} where sessionId = ? and memberId = ?", ChatSessionMemberPoJo.CACHE_NAME);
        ChatSessionMemberPoJo memberPoJo = IgniteQueryHelper.findOneBy(sql, new Object[]{request.getTid(), currentUserId},
                ChatSessionMemberPoJo.class, sessionMemberCache);
        if (memberPoJo != null) {
            memberPoJo.setDoNotDisturb(request.getNoDisturbing());
            sessionMemberCache.put(memberPoJo.getKey(), memberPoJo);
        }
        return ExecuteResult.failure("未找到用户在群中的相关记录", false);
    }

    public IExecuteResult<QueryGroupInfoResponse> queryGroupList(Long currentUserId, int pageIndex, int pageSize, Principal principal) {
        String tmpSql = MessageFormat.format("select count(*) from {0} where deleted = false and agreeStatus = ? and memberId = ?",
                ChatSessionMemberPoJo.CACHE_NAME);
        //统计当前用户加入的群组总数。
        long total = _totalCount(tmpSql, 1, currentUserId);

        List<GroupInfo> groupInfos = new ArrayList<>();
        QueryGroupInfoResponse response = new QueryGroupInfoResponse();
        response.setTotalRecords(total);
        response.setGroupInfos(groupInfos);

        String sql = MessageFormat.format("select * from {0} where deleted = false and agreeStatus = ? and memberId = ? limit ?, ?", ChatSessionMemberPoJo.CACHE_NAME);
        SqlFieldsQuery query = new SqlFieldsQuery(sql).setArgs(1, currentUserId, pageIndex, pageSize);
        try (FieldsQueryCursor<List<?>> cursor = sessionMemberCache.query(query)) {
            List<ChatSessionMemberPoJo> sessionMemberPoJos = new ArrayList<>();
            StoreSqlHelper.convertTo(cursor, sessionMemberPoJos, ChatSessionMemberPoJo.class);
            for (ChatSessionMemberPoJo sessionMemberPoJo : sessionMemberPoJos) {
                //获取当前用户在该群组中的各种状态数据。
                MemberStatusInGroup memberStatusInGroup = new MemberStatusInGroup();
                BeanUtils.copyProperties(sessionMemberPoJo, memberStatusInGroup);
                //检查并完善和头像属性
                if (!StringUtils.hasText(memberStatusInGroup.getMemberNick()) || !StringUtils.hasText(memberStatusInGroup.getAvatar())) {
                    IMUserPoJo userPoJo = userCache.get(sessionMemberPoJo.getMemberId());
                    if (!StringUtils.hasText(memberStatusInGroup.getMemberNick())) {
                        memberStatusInGroup.setMemberNick(userPoJo.getNickname());
                    }
                    if (!StringUtils.hasText(memberStatusInGroup.getAvatar())) {
                        memberStatusInGroup.setAvatar(userPoJo.getAvatar());
                    }
                }

                //获取该群组信息
                ChatSessionPoJo sessionPoJo = sessionCache.get(sessionMemberPoJo.getSessionId());
                GroupInfo groupInfo = new GroupInfo();
                groupInfos.add(groupInfo);
                BeanUtils.copyProperties(sessionPoJo, groupInfo);
                //为该群组信息对象设置用户状态属性。
                groupInfo.setMemberStatus(memberStatusInGroup);
                //设置用户在该群组是否是群主。
                memberStatusInGroup.setOwner(Objects.equals(sessionPoJo.getCreatorId(), currentUserId));
            }
        }
        return ExecuteResult.success(response);
    }

    private Long _totalCount(String sql, int agreeStatus, Long memberId) {
        SqlFieldsQuery query = new SqlFieldsQuery(sql).setArgs(agreeStatus, memberId);
        long total = 0;
        try (FieldsQueryCursor<List<?>> cursor = sessionMemberCache.query(query)) {
            List<List<?>> list = cursor.getAll();
            total = (long) list.get(0).get(0);
        }
        return total;
    }

    private int totalJoinApply(long currentUserId) {
        String sql = MessageFormat.format("select * from {0} where checkStatus = 1 and roleInSession <= 2 and memberId = ?",
                ChatSessionMemberPoJo.CACHE_NAME);
        List<ChatSessionMemberPoJo> memberPoJos = IgniteQueryHelper.findAllBy(sql, new Object[]{currentUserId}, ChatSessionMemberPoJo.class,
                sessionMemberCache);
        if (CollectionUtils.isEmpty(memberPoJos)) {
            return 0;
        }
        StringBuilder condition = new StringBuilder();
        List<Object> params = new ArrayList<>();
        for (ChatSessionMemberPoJo memberPoJo : memberPoJos) {
            condition.append(" or ").append("sessionId = ?");
            params.add(memberPoJo.getSessionId());
        }
        sql = MessageFormat.format("select count(*) from {0} where checkStatus = 0 and ({1})",
                ChatSessionMemberPoJo.CACHE_NAME, condition.substring(4));
        return (int) IgniteQueryHelper.getCountBy(sql, params.toArray(), sessionMemberCache);
    }

}
